home *** CD-ROM | disk | FTP | other *** search
Text File | 1995-04-28 | 12.6 KB | 524 lines | [TEXT/MMCC] |
- /*
- * DebugNew.cp
- *
- * Copyright © 1993 metrowerks inc. All rights reserved.
- *
- * A debugging layer over the standard operator new and delete
- * to detect memory overwrites, incorrect deletes, and memory leaks.
- * See DebugNew.doc in the CodeWarrior Examples:Debug New demo: for
- * background and usage instructions.
- */
-
- #include "DebugNew.h"
-
- // If leak checking is enabled, we need to define macros that
- // will allow us to replace the usual operator new and operator delete
- #if DEBUG_NEW >= DEBUG_NEW_BASIC
-
- #define OPERATOR_NEW _std_operator_new
- #define OPERATOR_DELETE _std_operator_delete
-
- void *_std_operator_new(size_t size);
- void *_std_operator_new(size_t size,void *p);
- void _std_operator_delete(void *ptr);
-
- #endif
-
- #include <New.cp>
-
- #if DEBUG_NEW >= DEBUG_NEW_BASIC
-
-
- // For every block allocated, we bump up the size to allow a header
- // and trailer to be put on the block. The header has bookkeeping info
- // to allow us to know if the block was probably a real allocated block,
- // and size info to allow us to find the trailer. The trailer is used to
- // detect overwrites.
- // NOTE: magic, unlikely values are used to tag blocks. This is a heuristic
- // only, since programs could legitimately be writing those same values through
- // wild pointers. When leak testing is on we can do better, since we have a
- // list of allocated blocks. The tag method provides reasonable checking
- // with less overhead.
-
- struct BlockHeader
- {
- #if DEBUG_NEW == DEBUG_NEW_LEAKS
- BlockHeader* next;
- const char* file;
- int line;
- #endif
- size_t size; // size of user's block in bytes, doesn't include prefix/suffix
- long tag; // magic value identifying the block
- };
-
- struct BlockTrailer
- {
- long tag; // magic value at end, used to detect overwrites
- };
-
- enum BlockStatus_t
- {
- blockValid, // valid allocated block
- blockFree, // free block
- blockPtrNull, // block pointer is NULL
- blockPtrOutOfRange, // block pointer outside application heap
- blockBadHeader, // block has invalid header
- blockBadTrailer, // block has invalid trailer
- blockNotInList, // block appears valid, but is not in block list (leak checking only)
- blockFreeOverwritten // dnDontFreeBlocks enabled, zapped free block was overwritten
- };
-
-
- static void DefaultErrorHandler(short);
- static void* alloc_block(size_t size, const char* file, int line);
- static void ZapBlock(void* ptr, register size_t len, register long value);
- static BlockStatus_t GetBlockStatus(char* ptr, Boolean fValidateFree);
- static void ReportBlockStatus(BlockStatus_t status);
-
- // global data used by DebugNew
-
- unsigned long gDebugNewAllocCount; // these three variables are public
- unsigned long gDebugNewAllocCurr; // total #bytes currently allocated
- unsigned long gDebugNewAllocMax; // max #bytes ever allocated
-
- static void* gHeapBegin;
- static void* gHeapEnd;
- static DebugNewErrorHandler_t gErrorHandler = DefaultErrorHandler;
-
- #if DEBUG_NEW == DEBUG_NEW_LEAKS
- static BlockHeader* gBlockList;
- #endif
-
- unsigned long gDebugNewFlags;
-
- #define BLOCK_HEADER_SIZE (sizeof(BlockHeader))
- #define BLOCK_TRAILER_SIZE (sizeof(BlockTrailer))
- #define BLOCK_OVERHEAD (BLOCK_HEADER_SIZE + BLOCK_TRAILER_SIZE)
-
- // Block zapping values. Newly allocated and free blocks are filled with
- // these values to detect uses of uninitialize memory and released memory.
- // Different values are used so you can look at a piece of memory
- // in the debugger and tell if it is allocated but uninitialized or
- // if it was released.
- #define ZAP_UNINITIALIZED 0xF1F1F1F1L
- #define ZAP_RELEASED 0xF3F3F3F3L
-
-
- // some random, hopefully not too common, odd values
- #define BLOCK_PREFIX_ALLOC 0xD1F3D1F3L
- #define BLOCK_PREFIX_FREE 0xF7B9F7B9L
- #define BLOCK_SUFFIX 0xB7D5B7D5L
-
-
- void *operator new(size_t size)
- {
- return alloc_block(size, 0, 0);
- }
-
- #if DEBUG_NEW == DEBUG_NEW_LEAKS
-
- void* operator new(size_t size, const char* file, int line)
- {
- return alloc_block(size, file, line);
- }
-
- #endif
-
- void operator delete(void* ptr)
- {
- // Allocation counter should be > zero. If not, then delete was
- // called too many times.
- if (!gDebugNewAllocCount)
- {
- gErrorHandler(dbgnewTooManyFrees);
- return;
- }
-
- BlockStatus_t status = GetBlockStatus((char*)ptr, false);
- switch (status)
- {
- case blockFree:
- case blockPtrOutOfRange:
- case blockBadHeader:
- case blockBadTrailer:
- ReportBlockStatus(status);
- return;
-
- case blockPtrNull: // it's legal to pass NULL to operator delete
- return;
- }
-
- BlockHeader* h = (BlockHeader*)((char*)ptr - BLOCK_HEADER_SIZE);
- const Boolean fDoFree = !(gDebugNewFlags & dnDontFreeBlocks);
-
- #if DEBUG_NEW == DEBUG_NEW_LEAKS
- // find the block in the block list
- BlockHeader* curr = gBlockList;
- BlockHeader* prev = 0;
- while (curr)
- {
- if (curr == h)
- break;
- prev = curr;
- curr = curr->next;
- }
- if (!curr)
- {
- gErrorHandler(dbgnewBlockNotInList);
- return;
- }
- else if (fDoFree)
- {
- if (prev)
- prev->next = curr->next;
- else
- gBlockList = curr->next;
- curr->next = 0;
- }
- #endif
- ZapBlock(ptr, h->size + BLOCK_TRAILER_SIZE, ZAP_RELEASED);
-
- // mark block freed, and free it
- h->tag = BLOCK_PREFIX_FREE;
- if (fDoFree)
- {
- --gDebugNewAllocCount;
- gDebugNewAllocCurr -= h->size;
- _std_operator_delete((char*)ptr - BLOCK_HEADER_SIZE);
- }
- }
-
- static void* alloc_block(size_t size, const char* file, int line)
- {
- // Use the standard allocator. It calls the new_handler if needed,
- // so if it fails, the allocation really failed.
-
- char* p = (char*) _std_operator_new(size+BLOCK_OVERHEAD);
- if (!p)
- return 0;
-
- ++gDebugNewAllocCount;
- gDebugNewAllocCurr += size;
- if (gDebugNewAllocCurr > gDebugNewAllocMax)
- gDebugNewAllocMax = gDebugNewAllocCurr;
-
- // zap data area + trailer, so blocks of sizes that are
- // not multiples of four get fully zapped.
-
- ZapBlock(p+BLOCK_HEADER_SIZE, size+BLOCK_TRAILER_SIZE, ZAP_UNINITIALIZED);
-
- BlockHeader* h = (BlockHeader*)p;
- h->size = size;
- h->tag = BLOCK_PREFIX_ALLOC;
-
- BlockTrailer* t = (BlockTrailer*)(p + size + BLOCK_HEADER_SIZE);
- t->tag = BLOCK_SUFFIX;
-
- #if DEBUG_NEW == DEBUG_NEW_LEAKS
- // new blocks go to front of the block list
- h->next = gBlockList;
- gBlockList = h;
- h->file = file;
- h->line = line;
- #endif
- return p+BLOCK_HEADER_SIZE;
- }
-
-
- static BlockStatus_t GetBlockStatus(char* ptr, Boolean fValidateFree)
- {
- if (!ptr)
- return blockPtrNull;
-
- // Validate that pointer is in the application heap. This
- // will also trap attempts to free pointers whose values
- // are inside uninitialized or free blocks, i.e.
- // 0xF1F1F1F1 or 0xF3F3F3F3.
-
- if ((void*)ptr < &ApplicationZone()->heapData || ptr > ApplicationZone()->bkLim)
- return blockPtrOutOfRange;
-
- const BlockHeader& h = *(BlockHeader*)(ptr - BLOCK_HEADER_SIZE);
-
- // check block header
-
- if (h.tag == BLOCK_PREFIX_FREE)
- {
- if (fValidateFree)
- {
- // Check that free block contains the zap values, if not
- // then it was probably overwritten through a dangling
- // pointer
-
- const unsigned long* p = (const unsigned long*) ((char*)&h + BLOCK_HEADER_SIZE);
- long count = (h.size+BLOCK_TRAILER_SIZE) / 4;
- while (--count >= 0)
- {
- if (*p++ != ZAP_RELEASED)
- return blockFreeOverwritten;
- }
- }
- return blockFree;
- }
-
- if (h.tag != BLOCK_PREFIX_ALLOC)
- return blockBadHeader;
-
- const BlockTrailer& t = *(BlockTrailer*) (ptr + h.size);
-
- // check block trailer
-
- if (t.tag != BLOCK_SUFFIX)
- return blockBadTrailer;
-
- return blockValid;
- }
-
- static void ReportBlockStatus(BlockStatus_t status)
- {
- switch (status)
- {
- case blockFree:
- gErrorHandler(dbgnewFreeBlock);
- break;
-
- case blockPtrNull:
- gErrorHandler(dbgnewNullPtr);
- break;
-
- case blockPtrOutOfRange:
- gErrorHandler(dbgnewPointerOutsideHeap);
- break;
-
- case blockBadHeader:
- gErrorHandler(dbgnewBadHeader);
- break;
-
- case blockBadTrailer:
- gErrorHandler(dbgnewBadTrailer);
- break;
-
- case blockNotInList:
- gErrorHandler(dbgnewBlockNotInList);
- break;
-
- case blockFreeOverwritten:
- gErrorHandler(dbgnewFreeBlockOverwritten);
- break;
- }
- }
-
- DebugNewErrorHandler_t DebugNewSetErrorHandler(DebugNewErrorHandler_t newHandler)
- {
- DebugNewErrorHandler_t oldHandler = gErrorHandler;
- if (newHandler)
- gErrorHandler = newHandler;
- return oldHandler;
- }
-
- static void DefaultErrorHandler(short err)
- {
- const unsigned char* s;
- switch (err)
- {
- case dbgnewNullPtr:
- s = "\pDebugNew: null pointer";
- break;
-
- case dbgnewTooManyFrees:
- s = "\pDebugNew: more deletes than news";
- break;
-
- case dbgnewPointerOutsideHeap:
- s = "\pDebugNew: delete or validate called for pointer outside application heap";
- break;
-
- case dbgnewFreeBlock:
- s = "\pDebugNew: delete or validate called for free block";
- break;
-
- case dbgnewBadHeader:
- s = "\pDebugNew: unknown block, or block header was overwritten";
- break;
-
- case dbgnewBadTrailer:
- s = "\pDebugNew: block trailer was overwritten";
- break;
-
- case dbgnewBlockNotInList:
- s = "\pDebugNew: block valid but not in block list (internal error)";
- break;
-
- case dbgnewFreeBlockOverwritten:
- s = "\pDebugNew: free block overwritten, could be dangling pointer";
- break;
-
- default:
- s = "\pDebugNew: undefined error";
- break;
- }
- DebugStr68k(s);
- }
-
-
- static void ZapBlock(void* ptr, register size_t len, register long value)
- {
- register long* p = (long*) ptr;
- len /= sizeof(long);
- while (len-- > 0)
- *p++ = value;
- }
-
- #if DEBUG_NEW == DEBUG_NEW_LEAKS
-
- void DebugNewValidateAllBlocks()
- {
- // traverse the block list, and validate every block
- BlockHeader* curr = gBlockList;
- while (curr)
- {
- BlockStatus_t status = GetBlockStatus((char*)curr + BLOCK_HEADER_SIZE, true);
- if (status != blockValid && status != blockFree)
- {
- ReportBlockStatus(status);
- // abort on any error. Not strictly necessary for some errors,
- // but we could crash if next field is trashed.
- break;
- }
- curr = curr->next;
- }
- }
-
- #include <stdio.h>
- #include <Files.h>
- #include <Strings.h>
- #include <Processes.h>
-
- static FILE *FSSpecOpen(FSSpec& fsspec, const char *mode)
- {
- FILE* file = 0;
- short savedVol;
- OSErr err;
-
- err = GetVol(0, &savedVol);
- if (err == noErr)
- {
- err = HSetVol(0, fsspec.vRefNum, fsspec.parID);
- if (err == noErr)
- {
- p2cstr(fsspec.name);
- file = fopen((char*)fsspec.name, mode);
- c2pstr((char*)fsspec.name);
- }
- SetVol(0, savedVol);
- }
- return file;
- }
-
-
- void DebugNewReportLeaks()
- {
- ProcessSerialNumber psn;
- OSErr err = GetCurrentProcess(&psn);
- if (err != noErr) return;
-
- ProcessInfoRec info;
- FSSpec spec;
-
- info.processInfoLength = sizeof(info);
- info.processName = 0;
- info.processAppSpec = &spec;
- err = GetProcessInformation(&psn, &info);
- if (err != noErr) return;
-
- StringPtr s = "\pleaks.log";
- BlockMove(s, spec.name, s[0]+1);
- FILE* f = FSSpecOpen(spec, "w");
- if (!f) return;
-
- long count = 0;
- long leakCount = 0;
- unsigned long bytesLeaked = 0;
- BlockHeader* curr = gBlockList;
- while (curr)
- {
- ++count;
- if (curr->tag == BLOCK_PREFIX_ALLOC)
- {
- bytesLeaked += curr->size;
- ++leakCount;
- }
- curr = curr->next;
- }
-
- if(count != gDebugNewAllocCount)
- fprintf(f, "Warning: length of block list different from count of allocated blocks (internal error).\n");
-
- fprintf(f, "Maximum #bytes allocated at any point via operator new: %ld\n", gDebugNewAllocMax);
-
- if (!leakCount)
- {
- fprintf(f, "No memory leaks.\n");
- return;
- }
- if (leakCount == 1)
- fprintf(f,"There is 1 memory leak of %lu bytes:\n", bytesLeaked);
- else
- fprintf(f,"There are %ld memory leaks, totaling %lu bytes:\n", leakCount, bytesLeaked);
-
- unsigned long totalAlloc = 0;
-
- curr = gBlockList;
- while (curr)
- {
- if (curr->tag == BLOCK_PREFIX_ALLOC)
- {
- if (curr->file)
- fprintf(f," %s line: %d, size: %lu\n", curr->file, curr->line, curr->size);
- else
- fprintf(f," <unknown>, size: %lu\n", curr->size);
- }
- totalAlloc += curr->size; // count freed blocks, since gDebugNewAllocCurr does
- curr = curr->next;
- }
- if (totalAlloc != gDebugNewAllocCurr)
- fprintf(f, "Warning: total allocations in block list different from gDebugNewAllocCurr.\n");
-
- fclose(f);
- }
- #else
- // leak checking disabled, does nothing
- void DebugNewReportLeaks()
- {
- }
- #endif // DEBUG_NEW_LEAKS
-
- #endif // DEBUG_NEW >= DEBUG_NEW_BASIC
-
- void DebugNewValidatePtr(void* ptr)
- {
- #if DEBUG_NEW >= DEBUG_NEW_BASIC
- BlockStatus_t status = GetBlockStatus((char*)ptr, true);
- #if DEBUG_NEW == DEBUG_NEW_LEAKS
- if (status == blockValid)
- {
- const BlockHeader* h = (BlockHeader*)((char*)ptr - BLOCK_HEADER_SIZE);
-
- // find the block in the block list
- BlockHeader* curr = gBlockList;
- while (curr)
- {
- if (curr == h)
- break;
- curr = curr->next;
- }
- if (!curr)
- status = blockNotInList;
- }
- #endif
- if (status != blockValid)
- ReportBlockStatus(status);
- #endif
- }
-
-